from 海龟策略实盘.config import *
from datetime import timedelta, datetime
import time

from 海龟策略实盘.api.position import bn_fetch_future_position
from 海龟策略实盘.factors import Signals
from 海龟策略实盘.utils.commons import retry_wrapper
from 海龟策略实盘.utils.dingding import send_dingding_msg_for_order, send_dingding_msg


# 根据最新数据，计算最新的signal
def calculate_signal(account_info, symbol_candle_data):
    """
    计算交易信号
    """

    # return变量
    symbol_signal = {
        '平多': [],
        '平空': [],
        '开多': [],
        '开空': [],
        '平多开空': [],
        '平空开多': [],
    }

    # 逐个遍历交易对
    for symbol in symbol_config.keys():

        # 赋值相关数据
        df = symbol_candle_data[symbol].copy()  # 最新数据
        # dir = lambda x: 1 if float(x) > 0 else (-1 if float(x) < 0 else 0)  # 当前持仓方向
        # now_pos = dir(account_info.at[symbol, '头寸数量'])
        now_pos = account_info.at[symbol, '持仓方向']  # 当前持仓方向
        avg_price = account_info.at[symbol, '持仓均价']  # 当前持仓均价

        # 需要计算的目标仓位
        target_pos = None

        # 根据策略计算出目标交易信号。
        if not df.empty:  # 当原始数据不为空的时候
            target_pos = getattr(Signals, symbol_config[symbol]
            ['strategy_name'])(df, symbol_config[symbol]['para'])
            account_info.at[symbol, '目标仓位'] = target_pos  # 这行代码似乎可以删除

        # 根据目标仓位和实际仓位，计算实际操作
        if now_pos == 1 and target_pos == 0:  # 平多
            symbol_signal['平多'].append(symbol)
        elif now_pos == -1 and target_pos == 0:  # 平空
            symbol_signal['平空'].append(symbol)
        elif now_pos == 0 and target_pos == 1:  # 开多
            symbol_signal['开多'].append(symbol)
        elif now_pos == 0 and target_pos == -1:  # 开空
            symbol_signal['开空'].append(symbol)
        elif now_pos == 1 and target_pos == -1:  # 平多，开空
            symbol_signal['平多开空'].append(symbol)
        elif now_pos == -1 and target_pos == 1:  # 平空，开多
            symbol_signal['平空开多'].append(symbol)

        account_info.at[symbol, '信号时间'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')  # 计算产生信号的时间

    # 删除没有信号的操作
    for key in list(symbol_signal.keys()):
        if not symbol_signal.get(key):
            del symbol_signal[key]

    return symbol_signal


# 根据交易所的限制(最小下单单位、量等)，修改下单的数量和价格
def modify_order_quantity_and_price(symbol, params):
    """
    根据交易所的限制(最小下单单位、量等)，修改下单的数量和价格
    """

    # 根据每个币种的精度，修改下单数量的精度
    params['quantity'] = round(params['quantity'], symbol_config[symbol]['最小下单量精度'])
    # 买单加价2%，卖单降价2%
    price_slippage = 0.002
    params['price'] = params['price'] * (1 + price_slippage) if params['side'] == 'BUY' else params['price'] * (1 - price_slippage)
    # 根据每个币种的精度，修改下单价格的精度
    params['price'] = round(params['price'], symbol_config[symbol]['最小下单价精度'])

    return params


# 针对某个类型订单，计算下单参数。供cal_all_order_info函数调用
def cal_order_params(signal_type, symbol, account_info):
    """
    针对某个类型订单，计算下单参数。供cal_all_order_info函数调用
    """

    params = {
        'symbol': symbol,
        'side': binance_order_type[signal_type],
        'price': account_info.at[symbol, '当前价格'],
        'type': 'LIMIT',
        'timeInForce': 'GTC',
    }

    if signal_type in ['平空', '平多']:
        params['quantity'] = abs(account_info.at[symbol, '持仓量'])

    elif signal_type in ['开多', '开空']:
        params['quantity'] = account_info.at[symbol, '分配资金'] * symbol_config[symbol]['leverage'] / \
                             account_info.at[symbol, '当前价格']

    else:
        close_quantity = abs(account_info.at[symbol, '持仓量'])
        open_quantity = account_info.at[symbol, '分配资金'] * symbol_config[symbol]['leverage'] / \
                        account_info.at[symbol, '当前价格']
        params['quantity'] = close_quantity + open_quantity

    # 修改精度
    print(symbol, '修改精度前', params)
    params = modify_order_quantity_and_price(symbol, params)
    print(symbol, '修改精度后', params)

    return params


# 计算所有币种的下单参数
def cal_all_order_info(symbol_signal, account_info):
    symbol_order_params = []

    # 如果没有信号，跳过
    if not symbol_signal:
        print('本周期无交易指令，不执行交易操作')
        send_dingding_msg('本周期无交易指令，不执行交易操作')
        return symbol_order_params

    # 如果只有平仓，或者只有开仓，无需重新更新持仓信息account_info
    if set(symbol_signal.keys()).issubset(['平空', '平多']) or set(symbol_signal.keys()).issubset(['开多', '开空']):
        print('本周期只有平仓或者只有开仓交易指令，无需再次更新账户信息，直接执行交易操作\n')

    # 如果有其他信号，需重新更新持仓信息account_info，然后据此重新计算下单量
    else:
        print('本周期有复杂交易指令(例如：平开、平和开、有平和平开、有开和平开)，需重新更新账户信息，再执行交易操作')

        # 更新账户信息account_info
        account_info = bn_fetch_future_position(account_info)

        # 标记出需要把利润算作保证金的仓位。
        for signal in symbol_signal.keys():
            for symbol in symbol_signal[signal]:
                account_info.at[symbol, '利润参与保证金比例'] = 1

        # 计算分配资金
        all_profit = account_info['持仓收益'].sum()  # 所有利润
        profit = (account_info['持仓收益'] * account_info['利润参与保证金比例']).sum()  # 参与保证金的利润
        balance = account_info.iloc[0]['账户权益'] - all_profit  # 初始投入资金
        balance = balance + profit  # 平仓之后的利润或损失
        account_info['分配资金'] = balance * account_info['分配比例']
        print(f'\n更新持仓信息、分配资金信息\n{account_info}\n')

    # 计算每个交易币种的各个下单参数
    for signal_type in symbol_signal.keys():
        for symbol in symbol_signal[signal_type]:
            params = cal_order_params(signal_type, symbol, account_info)

            if params['quantity'] == 0:  # 考察下单量是否为0
                print('\n', symbol, '下单量为0，忽略')
            elif params['price'] * params['quantity'] <= 5:  # 和最小下单额5美元比较
                print('\n', symbol, '下单金额小于5u，忽略')
            else:
                # 改成str
                params['price'] = str(params['price'])
                params['quantity'] = str(params['quantity'])
                symbol_order_params.append(params)

    return symbol_order_params


# ===批量下单
#  POST /fapi/v1/batchOrders (HMAC SHA256)
def place_binance_batch_order(symbol_order_params):
    num = 5  # 每个批量最多下单的数量
    for i in range(0, len(symbol_order_params), num):
        symbol_order_list = symbol_order_params[i:i + num]
        params = {
            'batchOrders': exchange.json(symbol_order_list),
            'timestamp': int(time.time() * 1000)
        }
        oder_res = retry_wrapper(exchange.fapiPrivatePostBatchOrders, params=params, func_name='币安批量下单')

        # 处理order_info数据
        df = pd.DataFrame(oder_res)
        df['update_time_GMT8'] = pd.to_datetime(df['updateTime'], unit='ms') + timedelta(hours=8)
        df = df[['symbol', 'orderId', 'price', 'avgPrice', 'side', 'origQty',
                 'executedQty', 'cumQuote', 'type', 'update_time_GMT8']]

        df.rename(columns={'price': '委托价格', 'avgPrice': '平均成交价', 'side': '方向',
                           'origQty': '原始委托数量', 'executedQty': '成交量',
                           'cumQuote': '成交金额', 'type': '订单类型'},
                  inplace=True)
        df.set_index('symbol', inplace=True)
        order_info = df

        # order_info_path = './data/order_info.csv'
        # order_info.to_csv(order_info_path, sep=',', index=True, header=True)
        print(f'\n订单信息为:\n{order_info}\n')
        send_dingding_msg_for_order(symbol_order_list, oder_res)
        time.sleep(short_sleep_time)
